package com.edt.ichannel.utils;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 * 封装了采用HttpClient发送HTTP请求的方法
 * @see 本工具所采用的是最新的HttpComponents-Client-4.2.1
 * @see 关于HttpClient,本博仅有两篇文章,另一篇是http://blog.csdn.net/jadyer/article/details/7615880
 * @see ===================================================================================================
 * @see 开发HTTPS应用时，时常会遇到两种情况
 * @see 1、测试服务器没有有效的SSL证书,客户端连接时就会抛异常
 * @see    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
 * @see 2、测试服务器有SSL证书,但可能由于各种不知名的原因,它还是会抛一堆烂码七糟的异常,诸如下面这几种
 * @see    javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
 * @see    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
 * @see    sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 * @see ===================================================================================================
 * @see 这里使用的是HttpComponents-Client-4.1.2创建的连接,所以就要告诉它使用一个不同的TrustManager
 * @see 由于SSL使用的模式是X.509,对于该模式,Java有一个特定的TrustManager,称为X509TrustManager
 * @see TrustManager是一个用于检查给定的证书是否有效的类,所以我们自己创建一个X509TrustManager实例
 * @see 而在X509TrustManager实例中,若证书无效,那么TrustManager在它的checkXXX()方法中将抛出CertificateException
 * @see 既然我们要接受所有的证书,那么X509TrustManager里面的方法体中不抛出异常就行了
 * @see 然后创建一个SSLContext并使用X509TrustManager实例来初始化之
 * @see 接着通过SSLContext创建SSLSocketFactory,最后将SSLSocketFactory注册给HttpClient就可以了
 * @see ===================================================================================================
 * @version v1.6
 * @history v1.0-->新建<code>sendGetRequest()</code>和<code>sendPostRequest()</code>方法
 * @history v1.1-->新增<code>sendPostSSLRequest()</code>方法,用于发送HTTPS的POST请求
 * @history v1.2-->新增<code>sendPostRequest()</code>方法,用于发送HTTP协议报文体为任意字符串的POST请求
 * @history v1.3-->新增<code>java.net.HttpURLConnection</code>实现的<code>sendPostRequestByJava()</code>
 * @history v1.4-->所有POST方法中增加连接超时限制和读取超时限制
 * @history v1.5-->重组各方法,并补充自动获取HTTP响应文本编码的方式,移除<code>sendPostRequestByJava()</code>
 * @history v1.6-->整理GET和POST请求方法,使之更为适用

 */
public class HttpClientUtil {
	private HttpClientUtil(){}
	
	protected static Logger logger = LoggerFactory
			.getLogger(HttpClientUtil.class);
	/**
	 * 发送HTTP_GET请求
	 * @see 1)该方法会自动关闭连接,释放资源
	 * @see 2)该方法会自动获取到响应消息头中[Content-Type:text/html; charset=GBK]的charset值作为响应报文解码字符集
	 * @see   若响应消息头中无Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1
	 * @param requestURL 请求地址(含参数)
	 * @return 远程主机响应正文
	 */
	public static String sendGetRequest(String reqURL){
		long respLen = 0;           //响应长度
		String respContent = null;  //响应内容
		Charset respCharset = null; //响应编码
		HttpClient httpClient = new DefaultHttpClient(); //创建默认的httpClient实例
		HttpGet httpGet = new HttpGet(reqURL);           //创建org.apache.http.client.methods.HttpGet
		try{
			HttpResponse response = httpClient.execute(httpGet); //执行GET请求
			HttpEntity entity = response.getEntity();            //获取响应实体
			if(null != entity){
				//使用EntityUtils.getContentCharSet(entity)也可以获取响应编码,但从4.1.3开始不建议使用这种方式
				respCharset = ContentType.getOrDefault(entity).getCharset();
				respLen = entity.getContentLength();  
				//respContent = EntityUtils.toString(entity, respCharset);
				respContent = EntityUtils.toString(entity);
				//Consume response content
				EntityUtils.consume(entity);
			}
			System.out.println("-----------------------------------------------------------------------");
			System.out.println("请求地址: " + httpGet.getURI());
			System.out.println("响应状态: " + response.getStatusLine());
			System.out.println("响应长度: " + respLen);
			System.out.println("响应编码: " + respCharset);
			System.out.println("响应内容: " + respContent);
			System.out.println("-----------------------------------------------------------------------");
		}catch(ClientProtocolException e){
			//该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合HTTP协议要求等
			logger.error("协议异常,堆栈轨迹如下", e);
		}catch(ParseException e){
			logger.error(e.getMessage(), e);
		}catch(IOException e){
			//该异常通常是网络原因引起的,如HTTP服务器未启动等
			logger.error("网络异常,堆栈轨迹如下", e);
		}finally{
			//关闭连接,释放资源
			httpClient.getConnectionManager().shutdown();
		}
		return respContent;
	}
	
	
	/**
	 * 发送HTTP_POST请求
	 * @see 1)该方法的优点在于-->允许自定义任何格式和内容的HTTP请求报文体,只要你熟悉HTTP/1.1协议
	 * @see 2)该方法会自动关闭连接,释放资源
	 * @see 3)方法内设置了连接和读取超时时间,单位为毫秒,超时后方法会自动返回空字符串
	 * @see 4)请求参数含中文等特殊字符时,可在传入前自行<code>URLEncoder.encode(string,encodeCharset)</code>,再指定encodeCharset为null
	 * @see 5)亦可指定encodeCharset参数值,由本方法代为编码
	 * @see 6)该方法在解码响应报文时所采用的编码,为响应消息头中[Content-Type:text/html; charset=GBK]的charset值
	 * @see   若响应消息头中未指定Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1
	 * @param reqURL        请求地址
	 * @param reqData       请求参数,若有多个参数则应拼接为param11=value11&param22=value22&param33=value33的形式
	 * @param encodeCharset 编码字符集,编码请求数据时用之,其为null表示请求参数已编码完毕,不需要二次编码
	 * @return 远程主机响应正文
	 */
	public static String sendPostRequest(String reqURL, String reqData, String encodeCharset){
		String reseContent = "";
		HttpClient httpClient = new DefaultHttpClient();
		httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, "10000");
		httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, "20000");
		HttpPost httpPost = new HttpPost(reqURL);
		try{
			if(null != reqData){
				if(reqData.contains("&")){
					List<NameValuePair> formParams = new ArrayList<NameValuePair>();
					for(String str : reqData.split("&")){
						formParams.add(new BasicNameValuePair(str.substring(0,str.indexOf("=")), str.substring(str.indexOf("=")+1)));
					}
					httpPost.setEntity(new StringEntity(URLEncodedUtils.format(formParams, encodeCharset)));
				}else{
					httpPost.setEntity(new StringEntity(reqData, encodeCharset));
				}
			}
			HttpResponse response = httpClient.execute(httpPost);
			HttpEntity entity = response.getEntity();
			if (null != entity) {
				//reseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
				reseContent = EntityUtils.toString(entity);
				EntityUtils.consume(entity);
			}
		} catch (ConnectTimeoutException cte){
			logger.error("与[" + reqURL + "]连接超时,自动返回空字符串");
		} catch (SocketTimeoutException ste){
			logger.error("与[" + reqURL + "]读取超时,自动返回空字符串");
		}catch(Exception e){
			logger.error("与[" + reqURL + "]通信异常,堆栈轨迹如下", e);
		}finally{
			httpClient.getConnectionManager().shutdown();
		}
		return reseContent;
	}
	
	/**
	 * HTTP通讯工具类
	 * @param url 请求的http地址
	 * @param requestCharSet 请求报文字符集
	 * @param rspondCharset 响应报文字符集
	 * @param map 参数
	 * @return List
	 * @throws IOException
	 */
	public static String URLPost(String url, String requestCharSet, String rspondCharset, Map map) throws IOException {
		logger.info("代码发送请求地址:"+url);
		URL sendurl = new URL(url);
		HttpURLConnection httpURLConnection = (HttpURLConnection) sendurl.openConnection();
		httpURLConnection.setRequestMethod("POST");
		httpURLConnection.setDoInput(true);
		httpURLConnection.setDoOutput(true);
		httpURLConnection.setUseCaches(false);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset="+requestCharSet);
		System.setProperty("sun.net.client.defaultConnectTimeout","10000");
		System.setProperty("sun.net.client.defaultReadTimeout", "20000");
		StringBuffer sb = new StringBuffer();
		Iterator it = map.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry pairs = (Map.Entry) it.next();
			sb.append(pairs.getKey()).append("=").
			append(URLEncoder.encode(pairs.getValue()==null?"":pairs.getValue().toString(),requestCharSet)).append("&");
		}//sign=6371f6ac0009e651305af9f781f3df07&payer=18602567347&seq=20141005164359164359&servername=TCOMQNUM&mode=M00028&version=10
		if (sb.length() > 0)// delete last & char
		{
			sb.setLength(sb.length() - 1);
		}
		// send data
		String sendData = sb.toString();
		logger.info("发送数据："+sendData);
		logger.info("发送字符串为-----"+url+"?"+sendData);
		OutputStream out = httpURLConnection.getOutputStream();
		out.write(sendData.getBytes("iso8859-1"));
		out.flush();
		out.close();
		InputStream in = httpURLConnection.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(in,rspondCharset));
		StringBuffer rsp = new StringBuffer();
		while (true) {
			String line = reader.readLine();
			if (line == null) {
				break;
			} else {
				rsp.append(line);
			}
		}
		reader.close();
		in.close();
		logger.info("发送url执行完毕");
		logger.info("请求结果："+rsp.toString());
		return rsp.toString();
	}
	/**
	 * HTTP通讯工具类
	 * @param url 请求的http地址
	 * @param requestCharSet 请求报文字符集
	 * @param rspondCharset 响应报文字符集
	 * @param map 参数
	 * @return List
	 * @throws IOException
	 */
	public static String URLGet(String url, String requestCharSet, String rspondCharset, Map map) throws IOException {
		URL sendurl = new URL(url);
		HttpURLConnection httpURLConnection = (HttpURLConnection) sendurl.openConnection();
		httpURLConnection.setRequestMethod("GET");
		httpURLConnection.setDoInput(true);
		httpURLConnection.setDoOutput(true);
		httpURLConnection.setUseCaches(false);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset="+requestCharSet);
		System.setProperty("sun.net.client.defaultConnectTimeout","10000");
		System.setProperty("sun.net.client.defaultReadTimeout","20000");
		StringBuffer sb = new StringBuffer();
		Iterator it = map.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry pairs = (Map.Entry) it.next();
			sb.append(pairs.getKey()).append("=").
			append(URLEncoder.encode(pairs.getValue().toString(),requestCharSet)).append("&");
		}
		if (sb.length() > 0)// delete last & char
		{
			sb.setLength(sb.length() - 1);
		}
		// send data
		String sendData = sb.toString();
		System.out.println("发送字符串为-----"+url+"?"+sendData);
		OutputStream out = httpURLConnection.getOutputStream();
		out.write(sendData.getBytes("iso8859-1"));
		out.flush();
		out.close();
		InputStream in = httpURLConnection.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(in,rspondCharset));
		StringBuffer rsp = new StringBuffer();
		while (true) {
			String line = reader.readLine();
			if (line == null) {
				break;
			} else {
				rsp.append(line);
			}
		}
		reader.close();
		in.close();
		return rsp.toString();
	}
	
	/**
	 * 发送HTTP_POST_SSL请求
	 * @see 1)该方法会自动关闭连接,释放资源
	 * @see 2)该方法亦可处理普通的HTTP_POST请求
	 * @see 3)方法内设置了连接和读取超时时间,单位为毫秒,超时后方法会自动返回空字符串
	 * @see 4)请求参数含中文等特殊字符时,可在传入前自行<code>URLEncoder.encode(string,encodeCharset)</code>,再指定encodeCharset为null
	 * @see 5)亦可指定encodeCharset参数值,由本方法代为编码
	 * @see 6)该方法在解码响应报文时所采用的编码,为响应消息头中[Content-Type:text/html; charset=GBK]的charset值
	 * @see   若响应消息头中未指定Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1
	 * @see 7)若reqURL的HTTPS端口不是默认的443,那么只需要在reqURL中指明其实际的HTTPS端口即可,而无需修改本方法内部指定的443
	 * @see   此时,方法默认会取指明的HTTPS端口进行连接.如reqURL=https://123.125.97.66:8085/pay/则实际请求即对方HTTPS_8085端口
	 * @param reqURL        请求地址
	 * @param params        请求参数
	 * @param encodeCharset 编码字符集,编码请求数据时用之,其为null表示请求参数已编码完毕,不需要二次编码
	 * @return 远程主机响应正文
	 */
	public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset){
		String responseContent = "";
		HttpClient httpClient = new DefaultHttpClient();
		//设置代理服务器
		//httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost("10.0.0.4", 8080));
		httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, "10000");
		httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, "20000");
		//创建TrustManager
		//用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
		X509TrustManager trustManager = new X509TrustManager(){
			public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
			public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
			public X509Certificate[] getAcceptedIssuers() {return null;}
		};
		//创建HostnameVerifier
		//用于解决javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
		X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier(){
			public void verify(String host, SSLSocket ssl) throws IOException {}
			public void verify(String host, X509Certificate cert) throws SSLException {}
			public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {}
			public boolean verify(String arg0, SSLSession arg1) {return true;}
		};
		try {
			//TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者，但它们使用的是相同的SSLContext
			SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
			//使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
			sslContext.init(null, new TrustManager[]{trustManager}, null);
			//创建SSLSocketFactory
			SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
			//通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
			httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
			//创建HttpPost
			HttpPost httpPost = new HttpPost(reqURL);
			//httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=UTF-8");
			//构建POST请求的表单参数
			if(null != params){
				List<NameValuePair> formParams = new ArrayList<NameValuePair>();
				for(Map.Entry<String,String> entry : params.entrySet()){
					formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
				}
				httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset));
			}
			HttpResponse response = httpClient.execute(httpPost);
			HttpEntity entity = response.getEntity();
			if (null != entity) {
//				//打印HTTP响应报文的第一行,即状态行
//				System.out.println(response.getStatusLine());
//				//打印HTTP响应头信息
//				for(Header header : response.getAllHeaders()){
//					System.out.println(header.toString());
//				}
				//responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
				responseContent = EntityUtils.toString(entity);
				EntityUtils.consume(entity);
			}
		} catch (ConnectTimeoutException cte){
			//Should catch ConnectTimeoutException, and don`t catch org.apache.http.conn.HttpHostConnectException
			logger.error("与[" + reqURL + "]连接超时,自动返回空字符串");
		} catch (SocketTimeoutException ste){
			logger.error("与[" + reqURL + "]读取超时,自动返回空字符串");
		} catch (Exception e) {
			logger.error("与[" + reqURL + "]通信异常,堆栈信息为", e);
		} finally {
			httpClient.getConnectionManager().shutdown();
		}
		return responseContent;
	}
}